import java.util.List;
import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.Logger;

import multiarray.MultiArray;
import multiarray.RecursiveAccess;


public class CoherenceModule implements CoherenceUpdater{
	
	private static Logger logger = Logger.getLogger("edu.hawaii.ccpv");

	static{
		ConsoleHandler ch = new ConsoleHandler();
		logger.addHandler(ch);
        // Request that every detail gets logged.
        logger.setLevel(Level.ALL);

	}

	private MultiArray<MultiArray<Float>> altruisms;
	private MultiArray<MultiArray<Float>> newAltruisms;
	private MultiArray<Float> powerByCrossSection;
	private List<List<int[]>> partition;
	private boolean[][] won_i_j;
	private MultiArray<Float> populationByCrossSection;	
	
	private float lambda = 0.5f;
	private float c = 0.5f;
	//private float I = 0.5f;
	private float epsilon = 0.1f;
	
	private int numCoalitions;
	private float[] sigma_i;
	private int[] coalitionSizes;
	private float[][] theta_i_j;
	private float[][][] tau_i_j_n;
	//private float[][] beta_i_j;
	private float[] pi_i;
	//private float[][][] phi_i_j_k;
	float[][] psi_i_j;
	
	float[][][][] betas;
	float[][][][] newBetas;
	float[][] averagedBetas;
	float[][][] freeRideWinningProb;
	float[][] contribWinningProb;
	
	
	float[][][] sumBeta_i_s;
	float[][][] sumBeta_j_s;

	
	public static CoherenceUpdater getCoherenceUpdater(CoherenceBuilder coherenceBuilder) {
		return new CoherenceModule(coherenceBuilder);
	}
	
	public CoherenceModule(CoherenceBuilder coherenceBuilder) {
		super();
		
		CoherenceInputsFromConflict conflictInputs = coherenceBuilder.getCoherenceInputsFromConflict();
		CoherenceInputsFromGlobals globalsInputs = coherenceBuilder.getCoherenceInputsFromGlobals();
		
		this.altruisms = conflictInputs.altruisms;
		this.powerByCrossSection = conflictInputs.powerByCrossSection;
		this.populationByCrossSection = conflictInputs.populationByCrossSection;
		this.partition = conflictInputs.partition;
		this.theta_i_j = conflictInputs.theta_i_j;
		this.won_i_j = conflictInputs.won_i_j;
		
		this.lambda = globalsInputs.lambda;
		this.c = globalsInputs.c;
		//this.I = globalsInputs.I;
		this.epsilon = globalsInputs.epsilon;
		
		//this.coalitionSizes = coalitionSizes;
		//this.numCoalitions = numCoalitions;
		//this.sigma_i = sigma_i;
		//this.theta_i_j = theta_i_j;
		//this.v_i_j = v_i_j;
		
		computeCoalitionSizes();
		computeSigmasAndPis();
		computeTaus();
		computeContribWinningProbs();
		computeFreeRideWinningProbs();
		computeBetas();
		computeSumBetas_i();
		computeSumBetas_j();
		computeNewBetas();
		newAltruisms = GetNewAltruismsFromBetas();
	}



	public static void main(String[] args) {
		
	}
	
	private void computeCoalitionSizes(){
		this.numCoalitions = partition.size();
		coalitionSizes = new int[numCoalitions];
		System.out.println("Number of coalitions: " + numCoalitions);
		logger.fine("Number of coalitions: " + numCoalitions);
		for(int i = 0; i < numCoalitions; i++){
			coalitionSizes[i] = partition.get(i).size();
			System.out.println("Coalition size " + i + " : " + coalitionSizes[i]);
		}
	}
	
	private void computeSigmasAndPis(){
		numCoalitions = partition.size();
		sigma_i = new float[numCoalitions];
		pi_i = new float[numCoalitions];
		for(int i = 0; i < numCoalitions; i++){
			List<int []> coalition = partition.get(i);
			for(int [] crossSection : coalition){
				sigma_i[i] += populationByCrossSection.getElementAtIndex(crossSection);
				pi_i[i] += sigma_i[i] * powerByCrossSection.getElementIndex(crossSection);
			}
		}
	}
	
	private void computeBetas(){
		
		betas = new float[numCoalitions][numCoalitions][][];
		averagedBetas = new float[numCoalitions][numCoalitions];
		
		for(int i = 0; i < numCoalitions; i++){
			List<int []> coalition_i = partition.get(i);
			for(int j = 0; j < numCoalitions; j++){
				List<int []> coalition_j = partition.get(j);
				
				float[][] result = new float[coalitionSizes[i]][coalitionSizes[j]];
				
				float sourceTotalPopulation = 0;
				float sourceTotalAltruism = 0;
				
				float[] targetAveragedAltruisms = new float[coalitionSizes[i]];
				
				for(int m = 0; m < coalitionSizes[i]; m++){
					float targetTotalAltruism = 0;
					float targetTotalPopulation = 0;
					for(int n = 0; n < coalitionSizes[j]; n++){
						result[m][n] = altruisms.
						getElementAtIndex(coalition_i.get(m)).
						getElementAtIndex(coalition_j.get(n));
						float targetPopulation = populationByCrossSection.
						getElementAtIndex(coalition_j.get(n));
						targetTotalAltruism += result[m][n] * targetPopulation;
						targetTotalPopulation += targetPopulation;
					}
					
					targetAveragedAltruisms[m] = targetTotalAltruism/targetTotalPopulation;
					float sourcePopulation = populationByCrossSection.
					getElementAtIndex(coalition_i.get(m));
					sourceTotalAltruism += targetAveragedAltruisms[m] * sourcePopulation;
					sourceTotalPopulation += sourcePopulation;
				}		
				betas[i][j] = result;
				averagedBetas[i][j] = sourceTotalAltruism/sourceTotalPopulation;
			}
		}
	}
	
	private void computeFreeRideWinningProbs(){
		freeRideWinningProb = new float[numCoalitions][numCoalitions][];
		for(int i = 0; i < numCoalitions; i++)
			for(int j = 0; j < numCoalitions; j++){
				float[] free_i_j = new float[coalitionSizes[i]];
				for(int n = 0; n < numCoalitions; n++){
					float numerator = (theta_i_j[i][j] - tau_i_j_n[i][j][n]);
					float denominator = (epsilon + theta_i_j[i][j] - tau_i_j_n[i][j][n] + theta_i_j[j][i]);
					free_i_j[n] = numerator/denominator;
				}
				freeRideWinningProb[i][j] = free_i_j;
			}
	}
	
	private void computeContribWinningProbs(){
		contribWinningProb = new float[numCoalitions][numCoalitions];
		for(int i = 0; i < numCoalitions; i++){
			for(int j = 0; j < numCoalitions; j++){
				float numerator = theta_i_j[i][j];
				float denominator = (epsilon + theta_i_j[i][j] + theta_i_j[j][i]);
				contribWinningProb[i][j] = numerator/denominator;
			}
		}
	}
	
	private void computeSumBetas_i(){
		sumBeta_i_s = new float[numCoalitions][numCoalitions][];
		for(int i = 0; i < numCoalitions; i++){
			for(int j = 0; j < numCoalitions; j++){
				sumBeta_i_s[i][j] = new float[coalitionSizes[i]];
				for(int m = 0; m < coalitionSizes[i]; m++){
					for(int n = 0; n < coalitionSizes[j]; n++){
						sumBeta_i_s[i][j][m] += betas[i][j][m][n]; 
					}
				}
			}
		}
	}
	
	private void computeSumBetas_j(){
		sumBeta_j_s = new float[numCoalitions][numCoalitions][];
		for(int i = 0; i < numCoalitions; i++){
			for(int j = 0; j < numCoalitions; j++){
				sumBeta_j_s[i][j] = new float[coalitionSizes[j]];
				for(int m = 0; m < coalitionSizes[i]; m++){
					for(int n = 0; n < coalitionSizes[j]; n++){
						sumBeta_j_s[i][j][n] += betas[i][j][m][n]; 
					}
				}
			}
		}		
	}
	
	private void computeNewBetas(){
		newBetas = new float[numCoalitions][numCoalitions][][];
		for(int i = 0; i < numCoalitions; i++){
			for(int j = 0; j < numCoalitions; j++){
				if(i == j) continue;
				newBetas[i][j] = new float[coalitionSizes[i]][coalitionSizes[j]];
				if(won_i_j[i][j]){
					contributeWinUpdate(i, j);
				} else {
					contributeLoseUpdate(i, j);		
				}
			}
		}
		
	}
	
	private void contributeLoseUpdate(int i, int j){
		for(int m = 0; m < coalitionSizes[i]; m++){
			for(int n = 0; n < coalitionSizes[j]; n++){
				newBetas[i][j][m][n] = betas[i][j][m][n] - sumBeta_j_s[i][j][n]/sigma_i[j] - c/lambda;
			}
		}
	}
	
	private void contributeWinUpdate(int i, int j){
		//System.out.println("called contributeWinUpdate");
		for(int m = 0; m < coalitionSizes[i]; m++){
			for(int n = 0; n < coalitionSizes[j]; n++){
				//float oldBeta = betas[i][j][m][n];
				float term1 = pi_i[i]*contribWinningProb[i][j] - 
					(pi_i[i] + tau_i_j_n[i][j][m])*freeRideWinningProb[i][j][m];
				float term2 = pi_i[j]*contribWinningProb[i][j] -
					pi_i[j]*freeRideWinningProb[i][j][m];
				float term3 = 2*pi_i[i]*contribWinningProb[i][j] - 
					(2*pi_i[j] + tau_i_j_n[i][j][m]* freeRideWinningProb[i][j][m]);
				float gamma_i_j_n = (sumBeta_j_s[i][j][n]/sigma_i[j]*term1)-
				(sumBeta_i_s[i][j][m]/sigma_i[i]*term2 + c*tau_i_j_n[i][j][m]/lambda)/
				term3;
				newBetas[i][j][m][n] = betas[i][j][m][n] + gamma_i_j_n;
			}
		}
	}
	
	private MultiArray<MultiArray<Float>> GetNewAltruismsFromBetas(){
		
		int[] dimensions = altruisms.getDimensions();
		int[] indices = new int[dimensions.length];
		//expand on all indices
		for(int i = 0; i < dimensions.length; i++) indices[i] = -1;
		MultiArray<MultiArray<Float>> result = new MultiArray<MultiArray<Float>>(dimensions);		
		List<int[]> permutations = RecursiveAccess.generateAllPermutations(indices, dimensions);
		
		//int i = 0;
		for(int[] permutation : permutations){
			
			MultiArray<Float> inner = new MultiArray<Float>(dimensions);
			result.setElementAtIndex(permutation, inner);
			//int j = 0;
			for(int[] innerPermutation : permutations){
				//inner.setElementAtIndex(innerPermutation, betas[i][j]);
				//j++;
			}		
			//i++;
		}
		
		for(int i = 0; i < numCoalitions; i++){
			List<int []> coalition_i = partition.get(i);
			for(int j = 0; j < numCoalitions; j++){
				List<int []> coalition_j = partition.get(j);
				
				float[][] betas_i_j = betas[i][j];
				
				for(int m = 0; m < coalitionSizes[i]; m++){
					for(int n = 0; n < coalitionSizes[j]; n++){
						float oldValue = altruisms.getElementAtIndex(coalition_i.get(m)).
						getElementAtIndex(coalition_j.get(n));
						float newValue = oldValue + betas_i_j[m][n];
						result.
						getElementAtIndex(coalition_i.get(m)).
						setElementAtIndex(coalition_j.get(n), newValue);
					}
				}		
			}
		}	
		
		return result;
			
	}
	
	private void computeTaus(){
		float[][][] result = new float[numCoalitions][numCoalitions][];
		for(int i = 0; i < numCoalitions; i++)
			for(int j = 0; j < numCoalitions; j++){
				result[i][j] = new float[coalitionSizes[i]];
				//all cross-sections in the i-th coalition
				//contribute the same resources towards conflict with
				//the jth coalition
				for(int k = 0; k < numCoalitions; k++)
					result[i][j][k] = theta_i_j[i][j]/sigma_i[i];
			}
		tau_i_j_n = result;
	}	

	@Override
	public MultiArray<MultiArray<Float>> getNewAltruisms(
			MultiArray<MultiArray<Float>> altruisms) {
		return newAltruisms;
	}	
	
}
